import 'dart:convert';
import 'dart:isolate';

import 'package:web_socket_channel/web_socket_channel.dart';

import '../pojo/Enums.dart';
import '../service/LoginService.dart';
import '../util/ImCacheFactory.dart';
import '../util/ImSingleton.dart';
import '../util/LoggerUtil.dart';
import 'dto/base/Resp.dart';
import 'dto/business/Login.dart';
import 'dto/business/PullData.dart';
import 'dto/business/RpcData.dart';

const String SPLIT = " ";
const String NEWLINE = "\r\n";

typedef DataCallBack = void Function(RpcData rpcData, Map<String, dynamic> sendDataMap);

DateTime lastedReceiveDataTime = DateTime.now();                         // 最近消息接受时间
DateTime lastedWriteDataTime = DateTime.now();                           // 最近消息发送时间

var _logger = createLogger();

abstract class ImWebSocketable {

  Future<void> start();

  Future<RpcData> sendData(RpcData rpcData, Function? function, int? timeout);

  void callbacks();

  void close();
}

Future<WebSocketChannel> websocketListener(String websocketUrl, Map<String, dynamic> sendDataMap,
    Map<String, dynamic> session, DataCallBack dataCallBack) async {
  final wsUrl = Uri.parse(websocketUrl);
  var webSocketChannel = WebSocketChannel.connect(wsUrl);
  await webSocketChannel.ready;
  _listener(webSocketChannel, sendDataMap, session, dataCallBack);

  _logger.i("websocket listener!!!");
  return webSocketChannel;
}

Future<LoginResp?> reconnection(WebSocketChannel? webSocketChannel, Map<String, dynamic> callbackMap,
    Map<String, dynamic> session) async {
  if (session.containsKey("authToken")) {
    var reConnection = ReConnection(authToken: session['authToken'], tenantCode: session['tenantCode'],
        terminalType: TerminalType.android.name);

    Map<String, String> extDataMap = {"localSeq": "2"};
    RpcData rpcData = RpcData("reconnection", 1, 0, extDataMap, reConnection);
    RpcData result = await _doSendMessageSync(webSocketChannel, callbackMap, session,
        SendData(rpcData: rpcData, decodeFunction: LoginResp.fromJson));
    _logger.i("websocket receive data: ${jsonEncode(result.data)}");

    var loginResp = result.data as LoginResp;

    ImSingleton.getInstance()?.loginSuccess(loginResp.userId!, loginResp.authCode, loginResp.token, loginResp.authToken,
        loginResp.tenantCode, loginResp.serverDateTime);

    var loginService = LoginService();

    await loginService.saveCacheLogin(loginResp, LoginType.password);
    await loginService.doLoginSuccessBusiness(loginResp);

    var user = await ImCacheFactory.getInstance().getImUser(loginResp.userId);
    ImSingleton.getInstance().session.putData("LoginUser", user!);

    return result.data as LoginResp;
  }
}

String encodeRpcData(RpcData rpcData, Map<String, dynamic> session) {
  if (session['authCode'] != null) {
    rpcData.extDataMap['authCode'] = session['authCode'];
  }
  String data = rpcData.data == null ? "{}" : jsonEncode(rpcData.data);
  String head = rpcData.path + SPLIT + rpcData.version.toString() + SPLIT + rpcData.status.toString() + SPLIT + NEWLINE;
  rpcData.extDataMap.forEach((key, value) {
    head += "$key:$value$NEWLINE";
  });
  String body = head + NEWLINE + data;
  return body;
}

bool sendMessage(WebSocketChannel? webSocketChannel, Map<String, dynamic> sendDataMap, Map<String, dynamic> session,
    SendData message, Function? noRespCallback) {
  RpcData rpcData = message.rpcData;
  String body = encodeRpcData(rpcData, session);

  lastedWriteDataTime = DateTime.now();
  webSocketChannel?.sink.add(body);
  String seq = rpcData.extDataMap["localSeq"].toString();
  assert(seq != null, "seq 不能为空");
  if (message.decodeFunction != null) {
    sendDataMap[seq] = InnerData(message, null);
  } else if (noRespCallback != null){
    noRespCallback(message);
  }
  _logger.i("websocket send message body $body");
  return true;
}

void callBackMessage(Map<String, dynamic> callbackMap, Map<String, dynamic> session, CallBackData message) {
  callbackMap[message.path] = message;
}

void _listener(WebSocketChannel? webSocketChannel, Map<String, dynamic> sendDataMap, Map<String, dynamic> session,
    DataCallBack dataCallBack) {
  webSocketChannel?.stream.listen((dynamic message) {
    lastedReceiveDataTime = DateTime.now();
    _logger.i("websocket receive message start $message");
    try {
      // 1，解析数据
      var content = message.toString();
      RpcData rpcData = _parseMessage(content, sendDataMap);

      // 2, 登录处理
      _loginHandler(rpcData, session);

      // 3, 回调
      dataCallBack(rpcData, sendDataMap);
    } catch (e, stackTrace) {
      _logger.e("websocket receive message handle error", error: e, stackTrace: stackTrace);
    } finally {
      _logger.i("websocket receive message end");
    }
  });
}

RpcData _parseMessage(String content, Map<String, dynamic> sendDataMap) {
  int idx = content.indexOf(NEWLINE);
  int secondIdx = content.indexOf(NEWLINE + NEWLINE);
  String headerStr = content.substring(0, idx);
  _logger.i("websocket receive message header : $headerStr");
  String extData = "";
  if (idx < secondIdx - 2) {
    extData = content.substring(idx+2, secondIdx);
    extData = extData.trim();
  }
  _logger.i("websocket receive message extData : $extData");
  String data = content.substring(secondIdx + 2);
  data = data.trim();
  RpcData rpcData = _decodeRpcData(sendDataMap, headerStr, extData, data);
  return rpcData;
}

void _loginHandler(RpcData rpcData, Map<String, dynamic> session) {
  if (rpcData.data is LoginResp) {
    LoginResp loginResp = rpcData.data as LoginResp;
    session['authToken'] = loginResp.authToken;
    session['userId'] = loginResp.userId;
    session['tenantCode'] = loginResp.tenantCode;
    session['authCode'] = "${loginResp.authCode}";
  }
}

RpcData _decodeRpcData(Map<String, dynamic> sendDataMap, String headStr, String extData, String data) {
  List<String> headers = headStr.trim().split(SPLIT);
  print("header: $headers");
  assert(headers.length == 3, "头部信息格式不对");
  String path = headers[0];
  int version = int.parse(headers[1]);
  int status = int.parse(headers[2]);

  Map<String, String> extDataMap = {};
  if (extData != null || extData != "") {
    print("extData: $extData");
    List<String> entryValues = extData.split(NEWLINE);
    entryValues.forEach((entry) {
      List<String> entryStr = entry.split(":");
      extDataMap[entryStr[0]] = entryStr[1];
    });
  }

  String seq = extDataMap['localSeq'].toString();
  if (status == -1) {
    var exceptionResp = ExceptionResp.fromJson(jsonDecode(data));
    RpcData rpcData = RpcData(path, version, status, extDataMap, exceptionResp);
    return rpcData;
  }
  assert(seq != null, "返回值中seq不能为空");

  Function? function;
  if (sendDataMap.containsKey(seq)) {
    InnerData innerData = sendDataMap[seq];
    if (innerData != null) {
      function = innerData.message.decodeFunction;
    }
  }

  if (function == null) {
    if (!sendDataMap.containsKey(path)) {
      _logger.e('缺少Router{$path}');
      throw Exception("请编写$path Router");
    }
    function = sendDataMap[path].decodeFunction;
  }
  assert(function != null, "function不能为空");

  var resultData = null;
  if (function != null) {
    resultData = data != '' && data != 'null' ? function(jsonDecode(data)) : OkResp(coder: 1, successDataTime: DateTime.now());
  } else {
    resultData = data != '' && data != 'null' ? jsonDecode(data) : OkResp(coder: 1, successDataTime: DateTime.now());
  }
  return RpcData(path, version, status, extDataMap, resultData);
}

Future _doSendMessageSync(WebSocketChannel? webSocketChannel, Map<String, dynamic> callbackMap,
    Map<String, dynamic> session, SendData message) {
  RpcData rpcData = message.rpcData;
  String body = encodeRpcData(rpcData, session);
  webSocketChannel?.sink.add(body);
  String seq = rpcData.extDataMap["localSeq"].toString();
  assert(seq != null, "seq 不能为空");
  ReceivePort receivePort = new ReceivePort();
  callbackMap[seq] = InnerData(message, receivePort.sendPort);
  _logger.i("websocket send message body $body");
  return receivePort.first;
}


class SendData {
  final RpcData rpcData;
  final Function? decodeFunction;

  SendData({required this.rpcData, required this.decodeFunction});
}

// 超时数据
class TimeoutData {
  final RpcData _rpcData;

  TimeoutData(this._rpcData);

  RpcData get rpcData => _rpcData;
}

// 服务端回调数据
class CallBackData {
  final String path;
  final Function decodeFunction;

  CallBackData({required this.path, required this.decodeFunction});
}

class InnerData<T extends SendData> {
  final T _message;
  final SendPort? _sendPort;

  InnerData(this._message, this._sendPort);

  T get message => _message;
  SendPort? get sendPort => _sendPort;
}